package net.mhelvens.deltaprofiles;

import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.BaseColumns;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v4.util.LongSparseArray;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.LinearLayout.LayoutParams;
import android.widget.TextView;

final class Rule {
    @SuppressWarnings("unused") private static final String TAG = Rule.class.getSimpleName();
	
	////////// Public Methods //////////////////////////////////////////////////
	
    ///// URIs /////
    
    public Uri uri            () { return uri_base().buildUpon().appendPath("/"+mID)       .build(); }
    public Uri uri_name       () { return uri()     .buildUpon().appendPath("/name")       .build(); }
    public Uri uri_description() { return uri()     .buildUpon().appendPath("/description").build(); }
    public Uri uri_icon       () { return uri()     .buildUpon().appendPath("/icon")       .build(); }
    public Uri uri_priority   () { return uri()     .buildUpon().appendPath("/priority")   .build(); }
    public Uri uri_condition  () { return uri()     .buildUpon().appendPath("/condition")  .build(); }
    
    ///// Mutators /////
    
	public Rule setName(String name) {
		assert !isDeleted();
		
		mName = name;
		
    	ContentValues values = new ContentValues();
    	values.put(KEY_name, mName);
    	DeltaProfilesDB.get().db().update(TABLE_main, values, KEY_id+" = "+mID, null);
		
		broadcast(ACTION_BROADCAST_MODIFY, uri_name());
		
		return this;
	}
	
	public Rule setDescription(String description) {
		assert !isDeleted();
		
		mDescription = description;
		
    	ContentValues values = new ContentValues();
    	values.put(KEY_description, mDescription);
    	DeltaProfilesDB.get().db().update(TABLE_main, values, KEY_id+" = "+mID, null);
		
		broadcast(ACTION_BROADCAST_MODIFY, uri_description());
		
		return this;
	}
	
	public Rule setIcon(Integer icon) {
		assert !isDeleted();
		
		mIcon = icon;
		
    	ContentValues values = new ContentValues();
    	values.put(KEY_icon, mIcon);
    	DeltaProfilesDB.get().db().update(TABLE_main, values, KEY_id+" = "+mID, null);
		
		broadcast(ACTION_BROADCAST_MODIFY, uri_icon());
		
		return this;
	}
	
	public Rule removeIcon() {
		assert !isDeleted();
		
		return setIcon(null);
	}
	
	public Rule grantPriorityOver(long other) {
		assert !isDeleted();
		
    	ContentValues values = new ContentValues();
    	values.put(KEY_higher, mID  );
    	values.put(KEY_lower , other);
    	DeltaProfilesDB.get().db().insertOrThrow(TABLE_priority, null, values);
		
		Bundle extras = new Bundle();
		extras.putLong("lower", other);
		broadcast(ACTION_BROADCAST_MODIFY, uri_priority(), extras);
		
		return this;
	}
	
	public Rule revokePriorityOver(long other) {
		assert !isDeleted();
		
		DeltaProfilesDB.get().db().delete(TABLE_priority, KEY_higher + " = " + mID
    			                              + " and " + KEY_lower  + " = " + other, null);

		Bundle extras = new Bundle();
		extras.putLong("lower", other);
		broadcast(ACTION_BROADCAST_MODIFY, uri_priority(), extras);
		
    	return this;
	}
	
	public Rule setCondition(Condition condition) {
		assert !isDeleted();

		mCondition = condition;
		
    	ContentValues values = new ContentValues();
    	values.put(KEY_condition, mCondition.id());
    	DeltaProfilesDB.get().db().update(TABLE_main, values, KEY_id+" = "+mID, null);
		
		broadcast(ACTION_BROADCAST_MODIFY, uri_condition());
		
		return this;
	}
	
	public Rule removeCondition() {
		setCondition(null);
		return this;
	}
	
	public void delete() {
		assert !isDeleted();
		
		DeltaProfilesDB.get().db().delete(TABLE_main, KEY_id + " = " + mID, null);
		mCache.delete(mID);

		mDeleted = true;
		mCache.delete(mID);
		
		broadcast(ACTION_BROADCAST_DELETE, uri());
	}
	
	///// Accessors /////
	
	public boolean isDeleted() {
		return mDeleted;
	}
	
	public long id() {
		assert !isDeleted();
		
		return mID;
	}
	
	public String name() {
		assert !isDeleted();
		
		refresh();
		return mName;
	}
	
	public String description() {
		assert !isDeleted();
		
		refresh();
		return mDescription;
	}
	
	public Integer icon() {
		assert !isDeleted();
		
		refresh();
		return mIcon;
	}
	
	public Adapter allRulesIHavePriorityOver(int flags) {
		assert !isDeleted();
		
		return new Adapter(
			DeltaProfilesDB.get().db().query(
				VIEW_lower_priority, null, KEY_higher+" = "+mID, null, null, null, null
			), flags
		);
	}
	
	public Adapter allRulesICouldGetPriorityOver(int flags) {
		assert !isDeleted();
		
		return new Adapter(
			DeltaProfilesDB.get().db().query(
				VIEW_candidate_lower_priority, null, KEY_higher+" = "+mID, null, null, null, null
			), flags
		);
	}
	
	////////// Private Fields //////////////////////////////////////////////////
	
	///// Bookkeeping /////
	
	private boolean mDeleted = false;
	private boolean mValid   = false;
	
	///// Unique Database ID /////
	
	private long mID;
	
	///// Data-items and Defaults /////
	
	private String    mName        = "";
	private String    mDescription = "";
	private Integer   mIcon        = null;
	private Condition mCondition   = null;

	////////// Private Methods /////////////////////////////////////////////////
	
	///// Constructors /////
	
	private Rule() {
    	mID = DeltaProfilesDB.get().db()
    		.insertOrThrow(TABLE_main, null, valuesForDB());
    	
		setValid();
		
		broadcast(ACTION_BROADCAST_ADD, uri());
	}
	
	private Rule(long id) {
		mID = id;
		
		setInvalid();
	}
	
	///// Utility Methods /////
	
	private ContentValues valuesForDB() {
		ContentValues result = new ContentValues();
		result.put(KEY_name       , mName                     );
		result.put(KEY_description, mDescription              );
		result.put(KEY_icon       , mIcon                     );
		result.put(KEY_condition  , Condition.idOf(mCondition));
		return result;
	}
	
	private void setValuesFromDB(Cursor c) {
		assert !c.isBeforeFirst() && !c.isAfterLast();
		
		mName        =               c.getString(c.getColumnIndex(KEY_name       )) ;
		mDescription =               c.getString(c.getColumnIndex(KEY_description)) ;
		mIcon        =               c.getInt   (c.getColumnIndex(KEY_icon       )) ;
		mCondition   = Condition.get(c.getLong  (c.getColumnIndex(KEY_condition  )));
	}
	
	private void broadcast(String action, Uri uri) { broadcast(action, uri, null); }
	private void broadcast(String action, Uri uri, Bundle extras) {
		LocalBroadcastManager.getInstance(mContext).sendBroadcast(
			new Intent(action, uri()).putExtra("id", mID)
		);
	}
	
	private void setInvalid() { mValid = false; }
	private void setValid  () { mValid = true ; }
	
	private void refresh() { refresh(null); }
	private void refresh(Cursor c) {
		assert !isDeleted();
		if (!mValid) {
			// is a positioned cursor supplied already? otherwise, perform query
			if (c == null) {
				c = DeltaProfilesDB.get().db().query(TABLE_main, null, KEY_id+" = "+mID, null, null, null, null);
				c.moveToFirst();
			}
			
			setValuesFromDB(c);
			
			setValid();
		}
	}

	////////// Static //////////////////////////////////////////////////////////
	
	///// Public Methods /////
	
	static public Uri uri_base() { return new Uri.Builder().path("/rules").build(); }
	
	static public void setContext(Context context) {
		mContext = context;
	}
	
	static public long count() {
		Cursor c = DeltaProfilesDB.get().db()
			.rawQuery("SELECT COUNT(*) FROM "+TABLE_main, null);
		c.moveToFirst();
		return c.getLong(0);
	}
	
    static public Rule getNew() {
    	Rule result = new Rule();
    	mCache.put(result.id(), result);
    	return result;
    }
    
    static public Adapter all(int flags) {
    	return new Adapter(DeltaProfilesDB.get().db().query(Rule.TABLE_main, null, null, null, null, null, null), flags);
    }
    
    static public Rule get(long id) { return get(id, null); }
	
	///// Private Fields /////
    
	static private Context               mContext;
	static private LongSparseArray<Rule> mCache = new LongSparseArray<Rule>();
    
	///// Broadcast Actions /////
	
	static public final String ACTION_BROADCAST_ADD    = "net.mhelvens.deltaprofiles.Rule.ACTION_BROADCAST_ADD";
	static public final String ACTION_BROADCAST_MODIFY = "net.mhelvens.deltaprofiles.Rule.ACTION_BROADCAST_MODIFY";
	static public final String ACTION_BROADCAST_DELETE = "net.mhelvens.deltaprofiles.Rule.ACTION_BROADCAST_DELETE";
	
	///// Database Constants /////
	
    //=====================================================================
    static private final String TABLE_main           = "rules";
    //---------------------------------------------------------------------
	static private final String KEY_id               = BaseColumns._ID;
    static private final String KEY_name             = "name";
    static private final String KEY_icon             = "icon";
    static private final String KEY_description      = "description";
    static private final String KEY_condition        = "condition";
    //=====================================================================
    static private final String TABLE_priority       = "priority";
    //---------------------------------------------------------------------
    static private final String KEY_higher           = "higher";
    static private final String KEY_lower            = "lower";
    //=====================================================================
    static private final String TABLE_trans_priority = "trans_priority";
    //---------------------------------------------------------------------
    static private final String KEY_trans_higher     = "higher";
    static private final String KEY_trans_lower      = "lower";
    //===========================================================================================
    static private final String VIEW_lower_priority           = "lower_priority_rules";
    //===========================================================================================
    static private final String VIEW_candidate_lower_priority = "candidate_lower_priority_rules";
    //===========================================================================================
    
	///// Private Utility Methods /////
	
    static private Rule get(long id, Cursor possibleCursor) {
    	if (!isCached(id))
    		mCache.put(id, new Rule(id));
    	
    	if (possibleCursor != null)
    		mCache.get(id).refresh(possibleCursor);
    	
    	return mCache.get(id);
    }
	
    static private boolean isCached(long id) {
    	return mCache.get(id, null) != null;
    }
    
	///// Public Adapter /////
	
	static public class Adapter extends BaseAdapter {
		
		///// Static /////
		
		static public abstract interface OnRuleViewDeleteClickListener {
			abstract void onDeleteClick(Rule rule);
		}
		
		///// Public /////
		
		static public final int FLAG_NAME          = 0x00010000;
		static public final int FLAG_DESCRIPTION   = 0x00020000;
		static public final int FLAG_ICON          = 0x00040000;
		static public final int FLAG_DELETE_BUTTON = 0x00080000;

		///// Private Fields /////

		private int                           mFlags;
		private Cursor                        mCursor;
		private OnRuleViewDeleteClickListener mOnDeleteClickListener;
		
		///// Private Methods /////
		
		private boolean flagged(int mask) { return ((mFlags & mask) == mask); }
		
		private Adapter(Cursor cursor, int flags) {
			mCursor = cursor;
			mFlags = flags;
		}

		///// Public Methods /////
		
		@Override
		public int getCount() {
			return mCursor.getCount();
		}
		
		@Override
		public Rule getItem(int position) {
			mCursor.moveToPosition(position);
			
			long id = mCursor.getLong(mCursor.getColumnIndex(Rule.KEY_id));
			
	    	return get(id, mCursor);
		}
		
		@Override
		public long getItemId(int position) {
			return getItem(position).id();
		}
		
		@Override
		public View getView(final int position, View view, ViewGroup parent) {
			final Rule rule = getItem(position);
			
			// possibly reuse view, otherwise inflate new one
			//
			if (view == null) {
				view = ((LayoutInflater)mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE))
					.inflate(R.layout.rule_list_item, parent, false);
			}
			
			// Icon / button sizing
			//
			final LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
			layoutParams.gravity = Gravity.CENTER_VERTICAL;
			
			if (!flagged(FLAG_NAME | FLAG_DESCRIPTION))
				layoutParams.height = layoutParams.width =
					mContext.getResources().getDimensionPixelSize(R.dimen.small_icon_height);
			
			// Name
			//
			TextView nameView = (TextView) view.findViewById(R.id.name);
			if (flagged(FLAG_NAME)) {
				nameView.setText(rule.name());
			} else {
				nameView.setVisibility(View.GONE);
			}
			
			// Description
			//
			TextView descriptionView = (TextView) view.findViewById(R.id.description);
			if (flagged(FLAG_DESCRIPTION) && !rule.description().isEmpty()) {
				descriptionView.setText(rule.description());
			} else {
				descriptionView.setVisibility(View.GONE);
			}
			
			// Icon
			//
			ImageView iconView = (ImageView) view.findViewById(R.id.icon);
			if (flagged(FLAG_ICON)) {
				iconView.setLayoutParams(layoutParams);
				// TODO
			} else {
				iconView.setVisibility(View.GONE);
			}
			
			// Delete button
			//
			ImageButton deleteButton = (ImageButton) view.findViewById(R.id.delete_button);
			deleteButton.setFocusable(false);
			if (flagged(FLAG_DELETE_BUTTON)) {
				deleteButton.setLayoutParams(layoutParams);
				deleteButton.setOnClickListener(new View.OnClickListener() {
					public void onClick(View v) {
						if (mOnDeleteClickListener != null)
							mOnDeleteClickListener.onDeleteClick(getItem(position));
					}
				});
			} else {
				deleteButton.setVisibility(View.GONE);
			}
			
			return view;
		}
		
		public Adapter setOnDeleteClickListener(OnRuleViewDeleteClickListener listener) {
			mOnDeleteClickListener = listener;
			return this;
		}
		
	} ///// Rule.Adapter
	
} ///// Rule
